[id].vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <template>
  2. <div>
  3. <div class="admin--page-content">
  4. <div class="admin--form">
  5. <!-- 낚시지역 상세 -->
  6. <table class="admin--form--table">
  7. <colgroup>
  8. <col style="width: 120px;">
  9. <col>
  10. </colgroup>
  11. <tbody>
  12. <tr>
  13. <th><div>지역명</div></th>
  14. <td>{{ formData.name || "-" }}</td>
  15. </tr>
  16. <tr>
  17. <th><div>등록일</div></th>
  18. <td>{{ formatDateTime(formData.created_at) }}</td>
  19. </tr>
  20. <tr>
  21. <th><div>최근 수정</div></th>
  22. <td>{{ formatDateTime(formData.updated_at) }}</td>
  23. </tr>
  24. </tbody>
  25. </table>
  26. <!-- 버튼 영역 -->
  27. <div class="admin--form-actions">
  28. <button type="button" class="admin--btn" @click="goToList">
  29. ← 목록으로
  30. </button>
  31. <button type="button" class="admin--btn admin--btn-red-border ml--auto" @click="handleDelete">
  32. 삭제
  33. </button>
  34. <button type="button" class="admin--btn admin--btn-red" @click="goToEdit">
  35. 수정
  36. </button>
  37. </div>
  38. <!-- 알림 모달 -->
  39. <AdminAlertModal
  40. v-if="alertModal.show"
  41. :title="alertModal.title"
  42. :message="alertModal.message"
  43. :type="alertModal.type"
  44. @confirm="handleAlertConfirm"
  45. @cancel="handleAlertCancel"
  46. @close="closeAlertModal"
  47. />
  48. </div>
  49. </div>
  50. <div class="admin--page-content mt--20">
  51. <div class="admin--sub--table--title">
  52. <p>해당 지역의 낚시어선 / 낚시터</p>
  53. <div class="sub--table--info">
  54. <span>🎣 낚시터 {{ fishingCount }}</span>
  55. <span>🚢 낚시어선 {{ onboardCount }}</span>
  56. </div>
  57. </div>
  58. <!-- 해당 지역의 어선 / 낚시터 -->
  59. <div class="admin--table-wrapper">
  60. <table class="admin--table">
  61. <thead>
  62. <tr>
  63. <th style="width: 80px;">번호</th>
  64. <th style="width: 180px;">구분</th>
  65. <th>이름</th>
  66. <th>주소</th>
  67. <th>상태</th>
  68. <th style="width: 120px;">등록일</th>
  69. </tr>
  70. </thead>
  71. <tbody>
  72. <tr v-if="isPlacesLoading">
  73. <td colspan="6" class="admin--table-loading">데이터를 불러오는 중...</td>
  74. </tr>
  75. <tr v-else-if="!places || places.length === 0">
  76. <td colspan="6" class="admin--table-empty">해당 지역에 등록된 낚시어선/낚시터가 없습니다.</td>
  77. </tr>
  78. <tr
  79. v-else
  80. v-for="(p, index) in places"
  81. :key="p.place_type + '-' + p.id"
  82. class="admin--table-row-clickable"
  83. @click="goToPlace(p)"
  84. >
  85. <td class="date">{{ (onboardCount + fishingCount) - index }}</td>
  86. <td>
  87. <span :class="['admin--badge', p.place_type === 'onboard' ? 'admin--badge-active' : 'admin--badge-html']">
  88. {{ p.place_type === "onboard" ? "낚시어선" : "낚시터" }}
  89. </span>
  90. </td>
  91. <td class="admin--table-title">{{ p.name }}</td>
  92. <td>{{ p.address || "-" }}</td>
  93. <td>
  94. <span :class="['admin--badge', p.status_YN === 'Y' ? 'admin--badge-active' : 'admin--badge-ended']">
  95. {{ p.status_YN === "Y" ? "사용중" : "미사용" }}
  96. </span>
  97. </td>
  98. <td class="date">{{ formatDate(p.created_at) }}</td>
  99. </tr>
  100. </tbody>
  101. </table>
  102. </div>
  103. <!-- 전체보기 버튼 -->
  104. <div class="admin--form" v-if="(onboardCount + fishingCount) > places.length">
  105. <div class="admin--form-actions">
  106. <button type="button" class="txt--btn ml--auto" @click="goToPlacesAll">
  107. {{ onboardCount + fishingCount }}건 전체보기 ->
  108. </button>
  109. </div>
  110. </div>
  111. </div>
  112. </div>
  113. </template>
  114. <script setup>
  115. import { ref, onMounted } from "vue";
  116. import { useRoute, useRouter } from "vue-router";
  117. import AdminAlertModal from "~/components/admin/AdminAlertModal.vue";
  118. definePageMeta({
  119. layout: "admin",
  120. middleware: ["auth"],
  121. });
  122. const route = useRoute();
  123. const router = useRouter();
  124. const { get, del } = useApi();
  125. const areaId = route.params.id;
  126. const formData = ref({
  127. name: "",
  128. created_at: "",
  129. updated_at: "",
  130. });
  131. // 해당 지역의 낚시어선 / 낚시터
  132. const places = ref([]);
  133. const onboardCount = ref(0);
  134. const fishingCount = ref(0);
  135. const isPlacesLoading = ref(false);
  136. // 알림 모달
  137. const alertModal = ref({
  138. show: false,
  139. title: "알림",
  140. message: "",
  141. type: "alert",
  142. onConfirm: null,
  143. });
  144. const showAlert = (message, title = "알림") => {
  145. alertModal.value = { show: true, title, message, type: "alert", onConfirm: null };
  146. };
  147. const showConfirm = (message, onConfirm, title = "확인") => {
  148. alertModal.value = { show: true, title, message, type: "confirm", onConfirm };
  149. };
  150. const closeAlertModal = () => { alertModal.value.show = false; };
  151. const handleAlertConfirm = () => {
  152. if (alertModal.value.onConfirm) alertModal.value.onConfirm();
  153. closeAlertModal();
  154. };
  155. const handleAlertCancel = () => closeAlertModal();
  156. // 상세 조회
  157. const loadDetail = async () => {
  158. const { data, error } = await get(`/area/${areaId}`);
  159. if (error || !data?.success) {
  160. showAlert(error?.message || data?.message || "조회에 실패했습니다.", "오류");
  161. return;
  162. }
  163. const row = data.data || {};
  164. formData.value = {
  165. name: row.name ?? "",
  166. created_at: row.created_at ?? "",
  167. updated_at: row.updated_at ?? "",
  168. };
  169. };
  170. // 삭제
  171. const handleDelete = () => {
  172. // 연결된 데이터 있으면 차단
  173. const total = onboardCount.value + fishingCount.value;
  174. if (total > 0) {
  175. showAlert(
  176. `해당 지역에 등록된 낚시어선/낚시터가 있어 삭제할 수 없습니다.\n(낚시어선 ${onboardCount.value} / 낚시터 ${fishingCount.value})\n\n먼저 연결된 어선/낚시터를 다른 지역으로 옮기거나 삭제해 주세요.`,
  177. "삭제 불가"
  178. );
  179. return;
  180. }
  181. showConfirm(
  182. `'${formData.value.name}' 낚시지역을 삭제하시겠습니까?`,
  183. async () => {
  184. const { data, error } = await del(`/area/${areaId}`);
  185. if (error || !data?.success) {
  186. showAlert(error?.message || data?.message || "삭제에 실패했습니다.", "오류");
  187. } else {
  188. showAlert(data.message || "삭제되었습니다.", "성공");
  189. setTimeout(() => router.push("/site-manager/area/list"), 800);
  190. }
  191. },
  192. "낚시지역 삭제"
  193. );
  194. };
  195. // 이동
  196. const goToList = () => router.push("/site-manager/area/list");
  197. const goToEdit = () => router.push(`/site-manager/area/edit/${areaId}`);
  198. const goToPlace = (p) => {
  199. if (p.place_type === "onboard") {
  200. router.push(`/site-manager/onboard/detail/${p.id}`);
  201. } else if (p.place_type === "fishing") {
  202. router.push(`/site-manager/fishing/detail/${p.id}`);
  203. }
  204. };
  205. const goToPlacesAll = () => router.push(`/site-manager/area/places/${areaId}`);
  206. // 해당 지역의 낚시어선/낚시터 로드 (최근 등록순 8개 + 카운트)
  207. const loadPlaces = async () => {
  208. isPlacesLoading.value = true;
  209. const { data, error } = await get(`/area/${areaId}/places`, { params: { limit: 8 } });
  210. if (!error && data?.success && data?.data) {
  211. places.value = data.data.items || [];
  212. onboardCount.value = data.data.onboard_count || 0;
  213. fishingCount.value = data.data.fishing_count || 0;
  214. }
  215. isPlacesLoading.value = false;
  216. };
  217. // 일시 포맷
  218. const formatDateTime = (dateString) => {
  219. if (!dateString) return "-";
  220. const date = new Date(dateString.replace(" ", "T"));
  221. if (isNaN(date.getTime())) return dateString;
  222. return date.toLocaleString("ko-KR", {
  223. year: "numeric",
  224. month: "2-digit",
  225. day: "2-digit",
  226. hour: "2-digit",
  227. minute: "2-digit",
  228. });
  229. };
  230. // 날짜만
  231. const formatDate = (dateString) => {
  232. if (!dateString) return "-";
  233. const date = new Date(dateString.replace(" ", "T"));
  234. if (isNaN(date.getTime())) return dateString;
  235. return date.toLocaleDateString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit" });
  236. };
  237. onMounted(() => {
  238. loadDetail();
  239. loadPlaces();
  240. });
  241. </script>